/**
 * 
 */
package name.yzhu.httpserver.handle;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import name.yzhu.common.CommonConstant;
import name.yzhu.common.ErrorCoder;
import name.yzhu.httpserver.HttpRequestType;
import name.yzhu.httpserver.HttpReturnData;
import name.yzhu.httpserver.HttpServerResponseException;
import name.yzhu.httpserver.IHttpPermission;
import name.yzhu.httpserver.log.LogEntity;
import name.yzhu.httpserver.log.LogHandler;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

import com.google.common.collect.Lists;
import com.google.common.net.HttpHeaders;

/**
 * @author yhzhu
 *
 */
public class URLHandle  extends AbstractHandler{
	private static final Log LOG = LogFactory.getLog(URLHandle.class);
	private List<URLHandleToMethod> handles = Lists.newArrayList();
	private List<URLHandleToMethod> rootHandleMethod = Lists.newArrayList();
	private IHttpPermission rootPermission = null;
	private ErrorCoder errorCoder = null;
	
	public URLHandle(){
		setRootHandle( new RootURLHandleProcess(this));
	}
	
	public List<URLHandleToMethod> getHandles() {
		return handles;
	}

	@Override
	public void handle(String target, Request baseRequest,
			HttpServletRequest req, HttpServletResponse res)
			throws IOException, ServletException {
		LOG.debug("target = "+target);
		HttpReturnData retData = new HttpReturnData();
		if (rootPermission != null && !rootPermission.check(req, retData )){
			String encode = retData.getHead(HttpHeaders.CONTENT_ENCODING);
			if (encode == null)
				encode = CommonConstant.UTF_8_STRING;
			res.getWriter().printf(new String(retData.getData(), encode));
			res.flushBuffer();
			return;
		}
		HttpRequestType type = HttpRequestType.byName(req.getMethod());
		for (URLHandleToMethod uhm : handles){
			HandleURLProcess process = uhm.getProcess();
			HttpRequestType rt = uhm.getHmd().getRequestType();
			if (rt == type){
				String pattern = process.getPattern();
				List<String> param = Lists.newArrayList();
				if (processPattern(target, pattern, param )){
						uhm.setExtParamValue(param);
						invokeURLHandle(req, res, uhm, type);
						
						return;
				}
			}
		}
		if (target.equals("/") && rootHandleMethod.size() > 0){
			for (URLHandleToMethod uhm : rootHandleMethod){
				HttpRequestType rt = uhm.getHmd().getRequestType();
				if (rt == type){
					invokeURLHandle(req, res, uhm, type);
					return;
				}
			}
		}
		
		res.setStatus(HttpServletResponse.SC_NOT_FOUND);		
	}
	
	private boolean processPattern(String target, String pattern,
			List<String> param) {
		Pattern p = Pattern.compile(pattern);
		Matcher m = p.matcher(target);
		if (m.find()){
		  if (m.groupCount() > 0){
			  for (int i=0; i<m.groupCount(); i++)
				  param.add(m.group(i+1));
		  }
		  return true;
		}
		return false;
	}

	private void invokeURLHandle(HttpServletRequest req,
			HttpServletResponse res, URLHandleToMethod uhm, HttpRequestType type) {
		LogHandler log = uhm.getProcess().getLogHandler(type);
		log.begin();
		log.setRequest(req);
		log.setRequestType(type);
		try{
			HttpReturnData retData = new HttpReturnData();
			if (uhm.getProcess().getHttpPermissionFactory().check(req, retData))
				retData = uhm.invokeFromURLHandle(req, res);
			log.setRetuData(retData);
			res.setStatus(retData.getStatus());
			res.setContentLength(retData.getData().length);
			res.setContentType("text/html; charset=UTF-8");
			res.setCharacterEncoding(CommonConstant.UTF_8_STRING);
			for (Entry<String, String> head  : retData.heads()){
				res.addHeader(head.getKey(), head.getValue());
			}
			String encode = retData.getHead(HttpHeaders.CONTENT_ENCODING);
			if (encode == null)
				encode = CommonConstant.UTF_8_STRING;
			res.getWriter().printf(new String(retData.getData(), encode));
			res.flushBuffer();
			LogEntity entity = log.getLogEntity();
			uhm.getProcess().paraseRetData(retData, entity);
		}
		catch(HttpServerResponseException e){
			LOG.error(e.getCode()+" "+ errorCoder.getErrorComment(e.getCode()), e);
			try {
				res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
			} catch (IOException e1) {
				LOG.error(e.getMessage(), e1);
			}
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			try {
				res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
			} catch (IOException e1) {
				LOG.error(e.getMessage(), e1);
			}
		}
		finally{
			log.end();
			
			log.persist(log.getLogEntity());
		}
	}

	static public boolean paraseURLPathParam(String target, String pattern, List<String> list){
		Pattern p = Pattern.compile(pattern);
		Matcher m = p.matcher(target);
		if (m.find()){
			for (int i=0; i<m.groupCount(); i++){
				list.add(m.group(i+1));
			}
			return true;
		}
		return false;
	}
	/**
	 * 
	 * @param process
	 */
	public void addHandleMethod(final HandleURLProcess process){
		Method[] methods = process.getClass().getMethods();
		for (Method method : methods){
			if (method.isAnnotationPresent(URLHandleAnno.class)){
				HandleMethodeDeclare hmd = HandleMethodeDeclare.getInstance(method); 
				URLHandleToMethod uhm = new URLHandleToMethod(method, process, hmd);
				handles.add(uhm);
			}
		}
	}
	
	public void setErrorCoder(final ErrorCoder errcode){
		this.errorCoder = errcode;
	}

	public void setRootHandle(final HandleURLProcess process) {
		rootHandleMethod.clear();
		Method[] methods = process.getClass().getMethods();
		for (Method method : methods){
			if (method.isAnnotationPresent(URLHandleAnno.class)){
				HandleMethodeDeclare hmd = HandleMethodeDeclare.getInstance(method); 
				URLHandleToMethod uhm = new URLHandleToMethod(method, process, hmd);
				rootHandleMethod.add(uhm);
			}
		}
	}

	public IHttpPermission getRootPermission() {
		return rootPermission;
	}

	public void setRootPermission(IHttpPermission rootPermission) {
		this.rootPermission = rootPermission;
	}
}
